package cn.hacynn.nd.service.impl;

import cn.hacynn.nd.common.constant.MessageConstant;
import cn.hacynn.nd.common.context.BaseContext;
import cn.hacynn.nd.common.enumeration.ShareStatus;
import cn.hacynn.nd.common.exception.FileException;
import cn.hacynn.nd.common.exception.ShareException;
import cn.hacynn.nd.common.properties.ShareProperties;
import cn.hacynn.nd.common.utils.CommonUtil;
import cn.hacynn.nd.common.utils.OSSUtil;
import cn.hacynn.nd.common.utils.RedisUtils;
import cn.hacynn.nd.mapper.FileMapper;
import cn.hacynn.nd.mapper.ShareMapper;
import cn.hacynn.nd.mapper.UserMapper;
import cn.hacynn.nd.pojo.dto.CodeVerifyDTO;
import cn.hacynn.nd.pojo.dto.SaveFileDTO;
import cn.hacynn.nd.pojo.dto.TransferFileDTO;
import cn.hacynn.nd.pojo.entity.File;
import cn.hacynn.nd.pojo.entity.Share;
import cn.hacynn.nd.pojo.entity.User;
import cn.hacynn.nd.pojo.vo.DownloadListVO;
import cn.hacynn.nd.service.FileService;
import cn.hacynn.nd.service.ShareService;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.extra.qrcode.QrCodeUtil;
import cn.hutool.extra.qrcode.QrConfig;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.TimeZone;

/**
 * @Author Hacynn
 * @Date 2024/5/28 22:36
 * @Version 1.0
 */
@Service
@Slf4j
public class ShareServiceImpl implements ShareService {

    @Autowired
    private ShareMapper shareMapper;
    @Autowired
    private RedisUtils redisUtils;
    @Autowired
    private ShareProperties shareProperties;
    @Autowired
    private UserMapper userMapper;
    @Autowired
    private FileMapper fileMapper;
    @Autowired
    private FileService fileService;
    @Autowired
    private OSSUtil ossUtil;
    @Autowired
    private CommonUtil commonUtil;

    /**
     * 获取分享列表
     * @return
     */
    @Override
    public List<Share> list(HttpServletRequest request) {
        commonUtil.getBaseContext(request);  // 从cookie中获取userID
        //获取用户信息
        Long userId = BaseContext.getCurrentId();
        //查询当前用户的所有分享列表
        List<Share> shares = shareMapper.selectList(new QueryWrapper<Share>().eq("user_id", userId));
        // 设置前端显示的时间
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm");
        simpleDateFormat.setTimeZone(TimeZone.getTimeZone("GMT+8"));
        for (Share share : shares) {
            // 分享状态
            share.setStatusDesc(ShareStatus.getDesc(share.getStatus()));
            // 创建时间
            share.setCreateTimeStr(simpleDateFormat.format(share.getCreateTime()));
            // 过期时间
            share.setExpireTimeStr(simpleDateFormat.format(share.getExpireTime()));
            // 从redis中获取分享代码
            String shareCode = (String) redisUtils.get(share.getId());
            // 设置分享链接，没有则为空
            share.setShareUrl((shareCode != null) ? (shareProperties.getLinkPrefix() + shareCode) : "");
            // 不需要该字段，置空返回
            share.setUrl("");
        }
        return shares;
    }

    /**
     * 获取分享链接信息
     * @param shortLink
     * @return
     */
    @Override
    public Share getShareDetail(String shortLink) {
        // 根据短链从redis中获取shareId
        String shareId = (String) redisUtils.get(shortLink);
        // 根据share:shareId从redis中获取文件的下载路径
        String url = (String)redisUtils.get(shareProperties.getRedisSharePrefix() + shareId);
        if (url == null) {   // 去redis中查询主要是用于判断是否到期
            // 分享文件不存在或已过期
            throw new ShareException(MessageConstant.SHARE_FILE_NOT_EXIST_OR_OVERDUE);
        }
        //根据shareId获取share信息
        Share share = shareMapper.selectById(shareId);
        if (share == null) {
            // 分享路径有误，分享列表无法查询到该分享
            throw new ShareException(MessageConstant.SHARE_PATH_MISTAKE);
        }
        // 每执行一次，都对浏览记录+1，进行更新
        int viewCount = share.getViewCount();
        share.setViewCount(++viewCount);
        shareMapper.updateById(share);
        // 设置前端显示的时间
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm");   // 去掉秒
        simpleDateFormat.setTimeZone(TimeZone.getTimeZone("GMT+8"));
        share.setExpireTimeStr(simpleDateFormat.format(share.getExpireTime()));
        // 过滤字段
        share.setFilePath("");
        share.setUserId(null);
        share.setCode("");
        return share;
    }

    /**
     * 检查分享文件是否已被删除
     * @param shareId
     * @return
     */
    @Override
    public void exist(String shareId) {
        //更加分享id查询分享信息
        Share share = shareMapper.selectById(shareId);
        if (share == null) {
            // 分享文件不存在
            throw new ShareException(MessageConstant.SHARE_FILE_NOT_EXIST);
        }
        //查询用户信息
        User user = userMapper.selectById(share.getUserId());
        if (user == null) {
            // 分享的用户不存在
            throw new ShareException(MessageConstant.SHARE_USER_NOT_EXIST);
        }
        File file = fileMapper.getFileByPath(user.getId(), share.getFilePath());
        //判断文件状态
        if (file == null) {
            // 分享的文件已被删除
            share.setStatus(ShareStatus.DELETED.getCode());  // 修改数据库的分享文件状态
            shareMapper.updateById(share);
            throw new ShareException(MessageConstant.SHARE_FILE_DELETED);
        }
    }

    /**
     * 设置分享文件提取码
     * @param shareId
     */
    @Override
    public String encode(String shareId) {
        //查询分享文件信息
        Share share = shareMapper.selectById(shareId);
        if (share == null) {
            // 分享文件不存在
            throw new ShareException(MessageConstant.SHARE_FILE_NOT_EXIST);
        }
        if (share.getIsEncoded()) {
            // 该分享文件已有提取码
            throw new ShareException(MessageConstant.SHARE_FILE_EXIST_CODE);
        }
        String code = RandomUtil.randomString(4);
        share.setIsEncoded(true);
        share.setCode(code);
        shareMapper.updateById(share);
        return code;
    }

    /**
     * 校验提取码
     * @param codeVerifyDTO
     * @return
     */
    @Override
    public Boolean verifyCode(CodeVerifyDTO codeVerifyDTO) {
        String shareId = codeVerifyDTO.getShareId();
        String code = codeVerifyDTO.getCode();
        if (code == null) {
            // 提取码为空
            throw new ShareException(MessageConstant.EXTRACT_CODE_IS_NULL);
        }
        Share share = shareMapper.selectById(shareId);
        if (share == null) {
            // 分享链接不存在
            throw new ShareException(MessageConstant.SHARE_PATH_NOT_EXIST);
        }
        return code.equals(share.getCode());
    }

    /**
     * 记录分享文件的下载次数
     * @param shareId
     */
    @Override
    public void downloadCount(String shareId) {
        //获取分享文件信息
        Share share = shareMapper.selectById(shareId);
        if (share == null) {
            // 分享文件不存在
            throw new ShareException(MessageConstant.SHARE_FILE_NOT_EXIST);
        }
        int count = share.getDownloadCount();
        share.setDownloadCount(++count);
        shareMapper.updateById(share);
    }

    /**
     * 分享的文件夹下载（打包下载文件），不需要登录
     * @param shareId
     * @param dirPath
     * @return
     */
    @Override
    public List<DownloadListVO> downloadDir(String shareId, String dirPath) {
        //查询分享文件信息
        Share share = shareMapper.selectById(shareId);
        if (share == null) {
            // 文件不存在
            throw new ShareException(MessageConstant.SHARE_FILE_NOT_EXIST);
        }
        //查询当前用户
        User user = userMapper.selectById(share.getUserId());
        if (user == null) {
            // 分享的用户不存在
            throw new ShareException(MessageConstant.SHARE_USER_NOT_EXIST);
        }
        File file = fileMapper.getFileByPath(user.getId(), dirPath);
        if (file == null) {
            // 文件夹不存在
            throw new FileException(MessageConstant.DIR_NOT_EXIST);
        }
        if (!file.getIsDir()) {
            // 不是文件夹
            throw new FileException(MessageConstant.NOT_FOLDER);
        }
        // 获得该文件夹的所有文件，包括自己
        List<File> files = fileService.getFilesByDir(user.getId(), file);
        List<DownloadListVO> downloadListVOList = new ArrayList<>();
        files.forEach(f -> {
            if (!f.getIsDir()) {   // 不是文件夹时
                String foldPath = f.getOssPath().substring(0, f.getOssPath().lastIndexOf('/') + 1);  // 文件所属文件夹路径
                DownloadListVO build = DownloadListVO.builder()
                        .url(ossUtil.generateUrl(user.getBucketName(), f.getOssPath(), new Date(System.currentTimeMillis() + 1000 * 60 * 60)))
                        .fileName(f.getName())
                        .foldPath(foldPath)
                        .build();
                downloadListVOList.add(build);
            }
        });
        return downloadListVOList;
    }

    /**
     * 取消分享
     * @param shareId
     */
    @Override
    public void delete(String shareId) {

        String key = shareProperties.getRedisSharePrefix() + shareId;
        if (!redisUtils.hasKey(key)) {
            // 没有该分享
            throw new ShareException(MessageConstant.SHARE_FILE_NOT_EXIST);
        }
        redisUtils.delete(key);
        redisUtils.delete((String)redisUtils.get(shareId));
        redisUtils.delete(shareId);
        shareMapper.deleteById(shareId);
    }

    /**
     * 生成分享二维码
     * @param shareUrl
     * @return
     */
    @Override
    public String generatorQR(String shareUrl) {
        //获取当前用户信息
        Long userId = BaseContext.getCurrentId();
        //它实例化了一个 QrConfig 类的对象，并将其赋值给变量 config。这个对象通常用于配置二维码生成的参数
        QrConfig config = new QrConfig();
        // 高纠错级别  许二维码在受到损坏、污损或部分遮挡时仍能被解码
        config.setErrorCorrection(ErrorCorrectionLevel.H);
        // 设置大小
        config.setHeight(400);
        config.setWidth(400);
        // 使用 QrCodeUtil 生成二维码，内容为 shareUrl，配置为 config，并将生成的二维码图片保存到 d:/NetWorkDiskQR/qrcodeCustom.jpg。
        QrCodeUtil.generate(shareUrl, config, FileUtil.file("d:/NetWorkDiskQR/qrcodeCustom.jpg"));
        //创建一个 File 对象，指向刚刚生成并保存到本地的二维码图片文件。
        java.io.File file = new java.io.File("d:/NetWorkDiskQR/qrcodeCustom.jpg");
        try {
            //用 ossUtil 的 uploadImg 方法，将本地文件上传到 OSS。
            return ossUtil.uploadImg(file, userId);
        } catch (Exception e) {
            // 二维码生成异常
            throw new ShareException(MessageConstant.QR_CODE_GENERATOR_EXCEPTION);
        }
    }

    /**
     * 转存文件
     * @param transferFileDTO
     */
    @Override
    public void transfer(TransferFileDTO transferFileDTO, HttpServletRequest request) {
        // 手动执行：从request中获取用户id
        commonUtil.getBaseContext(request);
        //获取用户信息
        Long userId = BaseContext.getCurrentId();
        if (userId == null) {
            // 用户未登录
            throw new ShareException(MessageConstant.USER_NOT_LOGIN);
        }
        String shareId = transferFileDTO.getShareId();
        String savePath = transferFileDTO.getSavePath();
        //获取分享文件的信息
        Share share = shareMapper.selectById(shareId);
        if (share == null) {
            // 分享的文件不存在
            throw new ShareException(MessageConstant.SHARE_FILE_NOT_EXIST);
        }
        // 开始转存文件
        User user = userMapper.selectById(userId);  // 获取当前转存的用户
        if (!share.getFileType().equals("dir")) {  // 如果是文件
            // 目的文件的路径
            String destPath = savePath + share.getFileName();  // test/ + file.jpg
            transferSingleFile(share, destPath, user.getBucketName());
        } else {   // 如果是文件夹
            File dir = fileMapper.getFileByPath(share.getUserId(), share.getFilePath());
            // 根据文件夹获取所有孩子节点（包括文件夹）
            List<File> files = fileService.getFilesByDir(share.getUserId(), dir);
            for (File f : files) {
                if (!f.getIsDir()) {  // 不是文件夹，就进行转存
                    Share s = Share.builder()
                            .userId(f.getUserId())
                            .filePath(f.getOssPath())
                            .size(f.getSize())
                            .fileName(f.getName())
                            .build();
                    // 目的文件的路径  TODO 存在缺陷，如果文件中存在纵向的文件名相同时，此操作无法确定获取到的文件名的深度
                    String destPath = savePath + f.getOssPath().substring(f.getOssPath().lastIndexOf(share.getFileName()));
                    transferSingleFile(s, destPath, user.getBucketName());
                }
            }
        }
        // 转存量+1
        Integer count = share.getTransferCount();
        share.setTransferCount(++count);
        shareMapper.updateById(share);
    }

    /**
     * 转存单个文件（包含了oss和数据库的转存操作）
     * @param share 转存的分享文件信息
     * @param destPath 目标路径
     * @param destBucket 目标bucket名
     */
    private void transferSingleFile(Share share, String destPath, String destBucket) {
        // 判断分享文件的用户是否存在
        User shareUser = userMapper.selectById(share.getUserId());
        if (shareUser == null) {
            // 分享的用户不存在
            throw new ShareException(MessageConstant.SHARE_USER_NOT_EXIST);
        }
        // 针对oss的转存
        ossUtil.copySameRegionBucketFile(shareUser.getBucketName(), share.getFilePath(), destBucket, destPath);
        // 针对数据库的转存
        SaveFileDTO saveFileDTO = SaveFileDTO.builder()
                .ossPath(destPath)
                .size(share.getSize())
                .name(share.getFileName())
                .build();

        fileService.saveFile(saveFileDTO);
    }
}